/*  
 * Copyright (c) 2010, James Daniello
 * This project is licensed under the 'New BSD' license.
 * The full text of the license can be found in the root
 * directory under the filename "LICENSE"
 */
package src.network;

import java.net.Socket;
import java.net.InetSocketAddress;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import src.gui.PICAIUClient;

/**
 * This class is the 'backend,' communicator object for the PICAIUClient. The PICAIUClient
 * uses this to communicate back and forth between the server for the game, and serverPersona
 * associated with this client. It runs as a thread to better use system resources.
 */
public class PICAIUClientComm extends Thread implements PICAIUCanPing {

  //The PICAIU server socket. Connects to server for game.
  private Socket server;
  //Stores a reference to the running instance of the PICAIUClient
  private PICAIUClient client;
  private PrintWriter out;
  private boolean running = true;
  private long sleepTimeMillis = 10;
  //Stores a reference to code for pinging to server.
  private PICAIUPingService pingService;

  /**
   * This class primarily sends data back and forth from the server. It interprets the data received
   * and either directs it to the GUI lobby, or sends commands to the client.
   * @param HOST         the host which the client wishes to connect to
   * @param PORT         the port on the server which the client wishes to conect to
   * @param gameClientIn the gameClient which houses this comm and the GUI
   */
  public PICAIUClientComm(String HOST, int PORT, PICAIUClient gameClientIn) {
    client = gameClientIn;

    pingService = new PICAIUPingService(this);

    //try connecting to server
    try {
      server = new Socket();
      server.connect(new InetSocketAddress(HOST, PORT), 5000);
      out = new PrintWriter(server.getOutputStream(), true);

      //could not connect to server
    } catch (Exception e) {
      System.out.println(e);
      running = false;
      client.setGUI("no server");
      //System.out.println("Could not connect to server.");
      //System.exit(1);
    }
  }

  /**
   * This method is threaded. As it runs it checks for new incoming data and processes it.
   * It will also update the ping service and send a ping if necessary or acts if the ping
   * takes too long. Additionally, it sleeps for a very short while. Sleeping has increased CPU usage
   * significantly as opposed to a non-sleeping version on a dual-core PC, even when the sleep time is
   * 10 milliseconds.
   */
  public void run() {
    try {
      if (server != null && server.getInputStream() != null) {
        BufferedReader serverIn = new BufferedReader(new InputStreamReader(server.getInputStream()));
        String stringIn;

        // while running, try getting data from server
        while (running) {

          pingService.update();

          //if data is to be had, process it, or else skip.
          if (serverIn.ready() && (stringIn = serverIn.readLine()) != null) {
            interpret(stringIn);
          }

          //Forces this thread to sleep so it won't hog resources. Even sleeping for 10ms changed the cpu
          //usage for the program from 50% to "0"%. I put 0 in quotes, because it definitely MUST use some
          //resources, but this is how windows 7 reported it.
          sleep(sleepTimeMillis);
        }
      }
      //could not get data from server
    } catch (Exception e) {
      System.out.println(e);
    }

  }

  /**
   * This server sends a direct 'text' message to server using the printwriter associated
   * with the server connection for this client. It will append 't,' a control string, to the
   * beginning of the message to indicate text is being sent and should be displayed in the gui.
   */
  public void sendMessageToServer(String message) {
    out.println("t " + message);
  }

  /**
   * This server sends a direct 'data' message to server using the printwriter associated
   * with the server connection for this client. It will append 'd,' a control string, to the
   * beginning of the message  to indicate a data control string has been sent.
   */
  public void sendDataToServer(String message) {
    out.println("d " + message);
  }

  /**
   * Returns the user name, or 'handle,' of the user running the client.
   */
  public String getUserName() {
    return client.getUserName();
  }

  /**
   * This method interprets the TCP strings that are received from the server. Each string begins with a
   * control character, either 't' for text, or 'd' for data. Text will be displayed in the gui, and data
   * will manipulate the program where allowed.
   */
  public void interpret(String message) {

    //if the debugging variable, VERBOSE, is set to true, output each message to the command line.
    if (PICAIUClient.VERBOSE) {
      //System.out.println("Recieved data from server: " + message);
    }

    //if a text message has been received, display in gui.
    if (message.startsWith("t ")) {
      client.writeToLobby(message.substring(2, message.length()));

      //else a data string has been received
    } else {
      //split the data string. All parts of the data string are delimited by the character '#'.
      String[] data = message.substring(2, message.length()).split("#");

      //'AUTHOK' means the connection has been authenticated and established
      if (data[0].equals("AUTHOK")) {
        client.setUserName(data[1]);
        client.saveServerList();
        client.setGUI("lobby");
        client.setConnected(true);
        PICAIUServer.UNITRETURNRATE = Float.parseFloat(data[2]);
        sendDataToServer("GETMYMONEY");
        //'RQ' means a request has been made
      } else if (data[0].equals("SETMYMONEY")) {
        client.setMyMoney(data[1]);
        //a new game has been authenticated
      } else if (data[0].equals("SETENEMYMONEY")) {
        String otherReady = "false";
        if (data.length == 4) {
          otherReady = data[3];
        }
        client.setEnemyMoney(data[1], data[2], otherReady);
        //a new game has been authenticated
      } else if (data[0].equals("GUI")) {
        client.setGUI(data[1]);
        //a new game has been authenticated
      } else if (data[0].equals("CREATEGAME")) {
        client.setGUI("pregame");
        //a new game has not been authenticated
      } else if (data[0].equals("CREATEGAMEFAILED")) {
        client.enableGameCreation();
        //a new game has been created, add it to the list of available games
      } else if (data[0].equals("ADDGAME")) {
        client.addGame(data[1], data[2]);
        //updates the client with the list of all the games
      } else if (data[0].equals("OTHERLEFTPREGM")) {
        client.otherUserLeftPreGame();
        //updates the client with the list of all the games
      } else if (data[0].equals("GMSLST")) {
        for (int i = 1; i < data.length; i++) {
          client.addGame(data[i]);
        }
        //removes the given game from the client
      } else if (data[0].equals("RMVGM")) {
        client.removeGame(data[1]);

      } else if (data[0].equals("CNCT")) {
        //if(data[1].equals("name")){
        //  sendDataToServer("SETNAME#"+getUserName());
        //  client.setConnected(true);
        //}
        client.enableLogin();
        //'USRLST' means the user list has been sent. each user is delimited by the char '#'.
      } else if (data[0].equals("USRLST")) {
        client.clearUserList();
        for (int i = 1; i < data.length; i++) {
          client.addUserToList(data[i]);
        }
        //'JOIN' means that a user has connected to the lobby.
      } else if (data[0].equals("JOIN")) {
        if (!data[1].equals(getUserName())) {
          client.addUserToList(data[1]);
        }
        //'RMVUSR' means that a user has disconnected from the lobby.
      } else if (data[0].equals("RMVUSR")) {
        client.removeUserFromList(data[1]);
        //'PONG' means the server is responding to a ping.
      } else if (data[0].equals("PONG")) {
        pingService.pongReceived();
        //'PING' means the server is pinging the client and the client needs to respond with a pong.
      } else if (data[0].equals("PING")) {
        if (PICAIUClient.VERBOSE) {
          System.out.println("SENDING PONG to server");
        }
        sendDataToServer("PONG");
        //'DSCNT' means that the client is being disconnected from the server. Likely an error has occured.
      } else if (data[0].equals("DSCNT")) {
        disconnect();
        //'ERR' means an error message has been received from the server.
      } else if (data[0].equals("ERR")) {
        System.out.println("ERROR! " + data[1]);
        if (data[1].equals("DB")) {
          client.error("DB");
        } else if (data[1].equals("DB2")) {
          client.setGUI("no server");
        }
      } else if (data[0].equals("CREATEOK")) {
        client.setGUI("login old");
        client.setLoginUser(data[1]);
      } else if (data[0].equals("CREATEFAIL")) {
        client.enableRegistration(data[1]);
      } else if (data[0].equals("RCVROK")) {
        client.setGUI("recover 2");
        client.setResetPassword2Email(data[1]);
      } else if (data[0].equals("RCVRFAIL")) {
        client.recoverFailed(data[1]);
      } else if (data[0].equals("VEROK")) {
        client.setGUI("recover 3");
      } else if (data[0].equals("VERFAIL")) {
        client.changeFailed(data[1]);
      } else if (data[0].equals("TEAM")) {
        client.setTeam(data[1]);
      } else if (data[0].equals("TYPES")) {
        String types = "";
        for (int i = 1; i < data.length; i++) {
          types += data[i] + "#";
        }
        client.setGameTypes(types.substring(0, types.length() - 1));
        //a unit has been successfully recruitted
      } else if (data[0].equals("REC")) {
        client.addUnit(data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
      } else if (data[0].equals("OTHERREADY")) {
        client.otherReady();
      } else if (data[0].equals("RMUNIT")) {
        client.removeUnitFromPreGame(data[1], data[2]);
        //start unit transfer
      } else if (data[0].equals("STRTUNTRSFR")) {
        client.initiateEngine();
        client.setNumberOfExpectedUnits(Integer.parseInt(data[1]));
        client.setMyMoney(data[2]);
        client.setTeam(data[3]);
        client.clearPreGame();
        client.setGUI("engine");
        sendDataToServer("CONTUNTRSFR");
        //ADD INCOMING UNIT
      } else if (data[0].equals("AIU")) {
        client.addIncomingUnit(message.substring(3));
        //both players finished placing, here are enemy troops;
      } else if (data[0].equals("AFP")) {
        client.setInitialEnemyPosition(data[1], Long.parseLong(data[2]), Float.parseFloat(data[3]), Float.parseFloat(data[4]));
      } else if (data[0].equals("OKGO")) {
        client.startEngine();
      } else if (data[0].equals("U")) {
        client.updateUnit(message);
      }

    }
  }

  /**
   * This method is necessary if this class has a pingService. The service will call this
   * to ping the server as necessary.
   * Overridden from PICAIUCanPing
   */
  @Override
  public void ping() {
    if (PICAIUClient.VERBOSE) {
      System.out.println("SENDING PING to server");
    }
    sendDataToServer("PING");
  }

  /**
   * This method indicates that the client wishes to let the server know that it wants to disconnect.
   */
  public void disconnectFromServer() {
    sendDataToServer("DSCNT#" + getUserName());
  }

  /**
   * Overridden from PICAIUCanPing interface and used to notify clientComm that a pong has not been received
   * within the time constraints.
   * Overridden from PICAIUCanPing
   */
  @Override
  public void pongNotReceived() {
    if (client.isInGame()) {
      return;
    }
    disconnect();
  }

  /**
   * This is a method used to cleanly sever the client/server relationship.
   */
  public void disconnect() {
    if (isConnected()) {
      client.writeToLobby("You Have Been Disconnected...");
    }
    client.clearUserList();
    client.clearGamesList();
    disconnectFromServer();
    client.setConnected(false);
    client.setGUI("lobby");
    client.setUserName("DISCONNECTED");
    client.setMyMoney("");
    //client.connect();
  }

  /**
   * Returns whether the client believes it is still connected to the server.
   * Overridden from PICAIUCanPing
   */
  @Override
  public boolean isConnected() {
    return client.isConnected();
  }

  /**
   * Returns whether this communication is still running
   */
  public boolean isRunning() {
    return running;
  }

  /**
   * Returns whether debugging output should be displayed to the command line.
   * Overridden from PICAIUCanPing
   */
  @Override
  public boolean isVerbose() {
    return PICAIUClient.VERBOSE;
  }
}
